package com.gmail.mariska.martin;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;

import javax.persistence.EntityManager;
import javax.persistence.EntityTransaction;
import javax.persistence.TypedQuery;
import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.CriteriaQuery;
import javax.persistence.criteria.Predicate;
import javax.persistence.criteria.Root;

import org.apache.log4j.Logger;
import org.apache.log4j.PropertyConfigurator;

import com.gmail.mariska.martin.jpa.Pertask;
import com.gmail.mariska.martin.jpa.dao.BankAccountDAO;
import com.gmail.mariska.martin.jpa.dao.DogDAO;
import com.gmail.mariska.martin.model.BankAccount;
import com.gmail.mariska.martin.model.Dog;
import com.gmail.mariska.martin.service.ConsoleWriterService;
import com.gmail.mariska.martin.service.DepositeAccountService;
import com.gmail.mariska.martin.service.PersistenceService;
import com.google.common.util.concurrent.MoreExecutors;
import com.google.common.util.concurrent.Service;
import com.google.common.util.concurrent.ServiceManager;
import com.google.common.util.concurrent.ServiceManager.Listener;

public class JPATutorialMain {
	static final Logger logger = Logger.getLogger(JPATutorialMain.class);

	List<Thread> threadList = new ArrayList<>();
	private final int countOfWorkers = 2;

	public void run() {
		Set<Service> services = createServices();
		final ServiceManager manager = createServiceManagerFromServices(services);
		setSaveShutdouwHook(manager);

		try {
			logger.info("starts servicies");
			manager.startAsync();
			logger.info("try awaiting for health state for 10s");
			manager.awaitHealthy(10, TimeUnit.SECONDS);
			logger.info("starting server execution");

			// importInitData();
			initData();
			createBalancedAccountTransfers();
			startAndAwaitThreads();
			showAllAccounts();

		} catch (Exception e) {
			logger.error("Main server error. ERR:" + e.getLocalizedMessage(), e);
		} finally {
			// shutdown hook shuld always stop
			manager.stopAsync().awaitStopped();
			logger.info("end of program");
		}

	}

	protected void startAndAwaitThreads() throws InterruptedException {
		for (Thread t : threadList) {
			t.start();
		}
		for (Thread t : threadList) {
			t.join(TimeUnit.SECONDS.toMillis(5));
		}
	}

	protected void createBalancedAccountTransfers() {
		for (int i = 0; i < countOfWorkers; i++) {
			threadList.add(new Thread(new ThreadDbWorker(1, 2)));
		}
		for (int i = 0; i < countOfWorkers; i++) {
			threadList.add(new Thread(new ThreadDbWorker(2, 1)));
		}
	}

	private void setSaveShutdouwHook(final ServiceManager manager) {
		Runtime.getRuntime().addShutdownHook(new Thread() {
			@Override
			public void run() {
				// Give the services 5 seconds to stop to ensure that we are responsive to shutdown
				// requests.
				try {
					logger.info("servicies at end of JVM stopping async");
					manager.stopAsync().awaitStopped(5, TimeUnit.SECONDS);
					logger.info("servicies at end of JVM stopped async");
				} catch (TimeoutException timeout) {
					// stopping timed out
					logger.error("stopping timeout. services terminated.");
				}
			}
		});
	}

	private ServiceManager createServiceManagerFromServices(Set<Service> services) {
		final ServiceManager manager = new ServiceManager(services);
		manager.addListener(new Listener() {
			@Override
			public void stopped() {
				logger.info("server stopped");
			}

			@Override
			public void healthy() {
				// code could be here
				logger.info("server is healthy and ready to start");
			}

			@Override
			public void failure(Service service) {
				logger.info("service(" + service + ") failiture. ErrMsg: " + service.failureCause().getMessage());
			}
		}, MoreExecutors.sameThreadExecutor());
		return manager;
	}

	private Set<Service> createServices() {
		Set<Service> services = new HashSet<>();
		PersistenceService persistenceService = new PersistenceService();
		services.add(new DepositeAccountService(persistenceService));
		services.add(persistenceService);
		services.add(new ConsoleWriterService());
		return services;
	}

	protected void initData() {
		EntityManager em = Pertask.getInstance().getEntityManager();
		BankAccountDAO dao = new BankAccountDAO(em);
		EntityTransaction transaction = em.getTransaction();
		transaction.begin();
		BankAccount entity = dao.find(1);
		entity.setValue(10000);
		dao.save(entity);
		BankAccount entity2 = dao.find(2);
		entity2.setValue(10000);
		dao.save(entity2);
		transaction.commit();
	}

	protected void importInitData() {
		EntityManager em = Pertask.getInstance().getEntityManager();
		BankAccountDAO dao = new BankAccountDAO(em);
		EntityTransaction transaction = em.getTransaction();
		transaction.begin();
		BankAccount entity = new BankAccount();
		entity.setValue(10000);
		dao.save(entity);
		BankAccount entity2 = new BankAccount();
		entity2.setValue(10000);
		dao.save(entity2);
		transaction.commit();
	}

	// public void run() {
	// // // Open a database connection (create a new database if it doesn't exist yet):
	// // Map<String, String> properties = new HashMap<String, String>();
	// // properties.put("javax.persistence.jdbc.user", "admin");
	// // properties.put("javax.persistence.jdbc.password", "admin");
	// // // EntityManagerFactory emf = Persistence.createEntityManagerFactory(
	// // // "objectdb://localhost:6136/myDbFile.odb", properties);
	// // EntityManagerFactory emf = null;
	// // EntityManager em = null;
	// try {
	// Perzistence.getInstance().init();
	// // emf = Persistence.createEntityManagerFactory("C:\\jpaTutorial.odb", properties); // or
	// // em = emf.createEntityManager();
	//
	// // EntityTransaction transaction = em.getTransaction();
	// //
	// // DogDAO dogDAO = new DogDAO(em);
	//
	// // experimetnsWithOneDog(em, transaction, dogDAO);
	// // experimetsWithFindAll(em, dogDAO);
	//
	// // dynamicCriteriaQuery(em);
	//
	// } finally {
	// // Close the database connection:
	// // em.close();
	// // emf.close();
	// Perzistence.getInstance().destroy();
	// }
	//
	// }

	private void showAllAccounts() {
		BankAccountDAO dao = new BankAccountDAO(Pertask.getInstance().getEntityManager());
		List<BankAccount> accs = dao.findAll();

		logger.debug("################################");
		for (BankAccount acc : accs) {
			logger.debug(acc);
		}
	}

	private void showAllDogs() {
		DogDAO dogDAO = new DogDAO(Pertask.getInstance().getEntityManager());
		List<Dog> dogs = dogDAO.findAll();

		for (Dog dog : dogs) {
			logger.debug(dog);
		}
	}

	protected void dynamicCriteriaQuery(EntityManager em) {
		Map<String, Object> keyValues = new HashMap<>();
		keyValues.put("name", "Alik4");
		// keyValues.put("weight", 30);

		CriteriaBuilder cb = em.getCriteriaBuilder();
		CriteriaQuery<Dog> cq = cb.createQuery(Dog.class);
		Root<Dog> dogRoot = cq.from(Dog.class);
		List<Predicate> predicatesForDog = new ArrayList<>();
		for (Entry<String, Object> entry : keyValues.entrySet()) {
			Predicate predicate = null;
			if (entry.getValue() instanceof String) {
				predicate = cb.like(dogRoot.<String> get(entry.getKey()), (String) entry.getValue());
			} else {
				predicate = cb.equal(dogRoot.get(entry.getKey()), entry.getValue());
			}
			predicatesForDog.add(predicate);
		}
		cq.select(dogRoot).where(predicatesForDog.toArray(new Predicate[predicatesForDog.size()]));

		TypedQuery<Dog> query = em.createQuery(cq);
		List<Dog> resultList = query.getResultList();
		logger.debug(resultList);
	}

	protected void experimetsWithFindAll(EntityManager em, DogDAO dogDAO) {
		List<Dog> dogs = dogDAO.findAll();

		for (Dog dog : dogs) {
			logger.debug(dog);
			if (dog.getId() == 4) {
				logger.debug("editting");
				dog.setName(dog.getName() + String.valueOf(dog.getId()));
				logger.debug(dog);
				em.getTransaction().begin();
				dogDAO.update(dog);
				em.getTransaction().commit();
			}
		}
	}

	protected void experimetnsWithOneDog(EntityManager em, EntityTransaction transaction, DogDAO dogDAO) {
		int dogId = 1;
		Dog dog = saveNewDog(transaction, dogDAO);
		dogId = dog.getId();
		Dog findedDog = dogDAO.find(dogId);
		logger.debug(findedDog);
		em.clear();
		findedDog = dogDAO.find(dogId);
		logger.debug(findedDog);
	}

	protected Dog saveNewDog(EntityTransaction transaction, DogDAO dogDAO) {
		transaction.begin();
		Dog dog = new Dog();
		dog.setName("Alik");
		dog.setWeight(30);
		logger.debug(dog);

		dogDAO.save(dog);
		logger.debug(dog);
		transaction.commit();
		logger.debug(dog);
		return dog;
	}

	public static void main(String[] args) {
		configureLog4jFromFile();
		new JPATutorialMain().run();
	}

	public static void configureLog4jFromFile() {
		PropertyConfigurator.configure("log4j.properties");
	}
}
